package com.gbase8c.dmt.db.opengauss;

import com.gbase8c.dmt.db.object.ConstraintObject;
import com.gbase8c.dmt.model.migration.config.Task;
import com.gbase8c.dmt.model.migration.dto.ConstraintDto;
import com.gbase8c.dmt.model.migration.dto.DataSourceDto;
import com.gbase8c.dmt.model.migration.dto.TableDto;
import com.gbase8c.dmt.model.migration.wrapper.KeywordsWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class ConstraintObjectImpl extends MetaImpl implements ConstraintObject {

    public ConstraintObjectImpl(DataSourceDto dataSourceDto) {
        super(dataSourceDto);
    }

    private static final Map<String, String> consTypeTo8cConsType = Maps.newHashMap();

    static {
        consTypeTo8cConsType.put(CHECK, "CHECK");
        consTypeTo8cConsType.put(PK, "PRIMARY KEY");
        consTypeTo8cConsType.put(UNIQUE, "UNIQUE");
        consTypeTo8cConsType.put(FK, "FOREIGN KEY");
    }

    @Override
    public List<String> getNames(Map<String, Object> params) {
        return null;
    }

    @Override
    public ConstraintDto get(String name, Map<String, Object> params) {
        return null;
    }

    @Override
    public ConstraintDto convert(ConstraintDto constraintDto, Map<String, Object> params) {
        String schema = constraintDto.getSchema().toLowerCase();
        boolean preserveCase = constraintDto.getTask().getMigrateConfig().isPreserveCase();
        Boolean convertible = Boolean.TRUE;
        String convertMsg = "已完成";
        constraintDto.setTarType(consTypeTo8cConsType.get(constraintDto.getConsType()));

        if (constraintDto.getConsType().equals(FK)) {
            List<String> tableNames = Lists.newArrayList();
            //查询目标库已存在表名
            List<String> tableNamesExisted = query("SELECT tablename FROM pg_tables WHERE schemaname = ?", new ColumnListHandler<String>(), schema);
            //得到迁移任务迁移表名
            List<String> tableNameList = (List<String>) params.get("tableNameList");
            //处理迁移表名的大小写问题
            List<String> tableNameListTemp = Lists.newArrayList();

            if (CollectionUtils.isNotEmpty(tableNameList)){
                for (String s : tableNameList){
                    s = wrap(s,preserveCase);
                    tableNameListTemp.add(s);
                }
            }

            tableNames.addAll(tableNamesExisted);
            tableNames.addAll(tableNameListTemp);
            if (!tableNames.contains(wrap(constraintDto.getReferencedTableName(), preserveCase))){
                convertible = Boolean.FALSE;
                log.info("referenced table does not exist in target database");
                convertMsg = "未迁移该外键所依赖的父表,无法迁移";
            }
        }

        Task task = constraintDto.getTask();
        KeywordsWrapper keywordsWrapper = new KeywordsWrapper(task.getKeywordsConfig());
        String tarTableName = keywordsWrapper.getTableName(constraintDto.getSchema(), constraintDto.getTableName(), preserveCase);

        for (TableDto.ColumnDto columnDto : constraintDto.getColumnDtos()) {
            String tarColumnName = keywordsWrapper.getColumnName(constraintDto.getSchema(),
                    constraintDto.getTableName(), columnDto.getName(),preserveCase);
            columnDto.setTarName(tarColumnName);
        }
        if (CollectionUtils.isNotEmpty(constraintDto.getReferencedColumnDtos())) {
            for (TableDto.ColumnDto columnDto : constraintDto.getReferencedColumnDtos()) {
                String tarColumnName = keywordsWrapper.getColumnName(constraintDto.getSchema(),
                        constraintDto.getTableName(), columnDto.getName(),preserveCase);
                columnDto.setTarName(tarColumnName);
            }
        }

        constraintDto.setTarTableName(tarTableName);
        constraintDto.setConvertible(convertible);
        constraintDto.setConvertMsg(convertMsg);

        return constraintDto;
    }

    private ConcurrentHashMap<String, Integer> names = new ConcurrentHashMap<>();

    private String formatName(String name) {
        String ret = null;
        if (StringUtils.isBlank(name)) {
            ret = checkAndGet(name, names);
        } else {
            if (name.getBytes(StandardCharsets.UTF_8).length <=56) {
                ret = checkAndGet(name, names);
            } else {
                name = subString(name);
                ret = checkAndGet(name, names);
            }
        }
        return ret;
    }

    private String subString(String name) {
        StringBuilder sb = new StringBuilder();
        int limit = 56;
        int bs = 0;
        for (int i=0; i<name.length(); i++) {
            String str = name.charAt(i) + "";
            int len = str.getBytes(StandardCharsets.UTF_8).length;
            bs += len;
            if (bs <= limit) {
                sb.append(str);
            } else {
                break;
            }
        }
        return sb.toString();
    }

    private String checkAndGet(String name, ConcurrentHashMap<String, Integer> names) {
        if (names == null) {
            names = new ConcurrentHashMap<>();
        }
        if (name == null) {
            name = UUID.randomUUID().toString().toLowerCase().replaceAll("-", "");
        }
        if (names.containsKey(name)) {
            for (int i = 1; i < Integer.MAX_VALUE; i++) {
                name = name + "_" + i;
                if (!names.containsKey(name)) {
                    names.put(name, 1);
                    break;
                }
            }
        } else {
            names.put(name, 1);
        }
        return name;
    }

    public static void main(String[] args) {
        String s = "aaa";
        System.out.println(s.getBytes(StandardCharsets.UTF_8).length);
    }

    @Override
    public String sql(ConstraintDto constraintDto, Map<String, Object> params) {
        StringBuilder sb = new StringBuilder();
        String createConstraintSql = null;
        String tarSchema = constraintDto.getTarSchema();
        String tarTableName = constraintDto.getTarTableName();
        String constraintName = constraintDto.getName();

        String indexsql = "select indexname from pg_indexes where schemaname = ?";
        String constraintSql = "select constraint_name from information_schema.table_constraints where constraint_schema = ?";

        List<String> indexNames = query(indexsql,new ColumnListHandler<>(),tarSchema);
        List<String> constraintNames = query(constraintSql,new ColumnListHandler<>(),tarSchema);

        boolean preserveCase = constraintDto.getTask().getMigrateConfig().isPreserveCase();

        //if (!constraintDto.getTarType().equals(oracleConsTypTo8cConsType.get("P"))){
        if (!constraintNames.contains(constraintName)){
            //此条命令定位到此schema下执行，解决建视图From语句后必须模式名.表名的情况
            sb.append("set search_path to ");
            sb.append(wrap(tarSchema, true));
            sb.append(";");

            sb.append(" alter table ");
            sb.append(wrap(tarTableName, preserveCase));
            sb.append(" add constraint ");
            String name = null;
            if (constraintDto.getConsType().equals(UNIQUE)){
                name = wrap(tarTableName + "_" + constraintName + "_uni", preserveCase);
            } else if(constraintDto.getConsType().equals(PK)){
                name = wrap(tarTableName + "_" + constraintName + "_pkey", preserveCase);
            } else {
                name = wrap(tarTableName + "_" + constraintName,preserveCase);
            }
            name = formatName(name);
            sb.append(name);

            sb.append(" ");
            sb.append(constraintDto.getTarType().toLowerCase());

            List<TableDto.ColumnDto> columnDtos = constraintDto.getColumnDtos();

            List<String> columnListTemp = Lists.newArrayList();
            for (TableDto.ColumnDto columnDto : columnDtos){
                String tarColumnName = columnDto.getTarName();
                tarColumnName = wrap(tarColumnName,preserveCase);
                columnListTemp.add(tarColumnName);
            }

//            //将列以逗号拆分.
//            List<String> columnList = Arrays.asList(StringUtils.split(constraintDto.getColumns(),","));
//            //拆分后单独处理各列名
//            for (String column : columnList){
//                column = wrap(column,preserveCase);
//                columnListTemp.add(column);
//            }

            //处理后重新以逗号拼接
            String columnNames = columnListTemp.stream().collect(Collectors.joining(","));

            if (constraintDto.getConsType().equals(UNIQUE)
                    || constraintDto.getConsType().equals(PK)){
                sb.append(" (");
                sb.append(columnNames);
                sb.append(")");
            }

            if (constraintDto.getConsType().equals(FK)){
                //将列以逗号拆分.
                List<String> referencedColumnList = Arrays.asList(StringUtils.split(constraintDto.getReferencedColumns(),","));
                //拆分后单独处理各列名
                List<String> referencedColumnListTemp = Lists.newArrayList();
//                for (String column : referencedColumnList){
//                    column = wrap(column,preserveCase);
//                    referencedColumnListTemp.add(column);
//                }

                List<TableDto.ColumnDto> referencedColumnDtos = constraintDto.getReferencedColumnDtos();
                for (TableDto.ColumnDto column : referencedColumnDtos){
                    String columnTarName = column.getTarName();
                    columnTarName = wrap(columnTarName,preserveCase);
                    referencedColumnListTemp.add(columnTarName);
                }

                //处理后重新以逗号拼接
                String referencedColumnNames = referencedColumnListTemp.stream().collect(Collectors.joining(","));
                sb.append(" (");
                sb.append(columnNames);
                sb.append(")");
                sb.append(" references ");
                sb.append(wrap(constraintDto.getReferencedTableName(),preserveCase));
                sb.append(" ");
                sb.append(" (");
                sb.append(referencedColumnNames);
                sb.append(")");

                if (null != constraintDto.getDeleteRule()) {
                    sb.append(" on delete ").append(constraintDto.getDeleteRule());
                }
                if (null != constraintDto.getUpdateRule()) {
                    sb.append(" on update ").append(constraintDto.getUpdateRule());
                }
            }

            if (constraintDto.getConsType().equals(CHECK)){
                String searchCondition = constraintDto.getSearchCondition();
                if (StringUtils.isNotBlank(searchCondition)){
                    if (!preserveCase) {
                        if (searchCondition.contains("\"")) {
                            //match double quotes
                            Pattern p = Pattern.compile("\"[^\"]*\"");
                            Matcher m = p.matcher(searchCondition);
                            List<String> list = Lists.newArrayList();
                            String result = null;
                            while(m.find()){
                                list.add(m.group());
                            }
                            if (CollectionUtils.isNotEmpty(list)){
                                result = list.get(0);
                            }
                            searchCondition = searchCondition.replace(result,result.toLowerCase());
                        }
                    }
                }
                sb.append(" (");
                sb.append(searchCondition);
//                sb.append(preserveCase? constraintDto.getSearchCondition() : constraintDto.getSearchCondition().toLowerCase());
//                sb.append(constraintDto.getSearchCondition());
                sb.append(")");
            }
        }

        createConstraintSql = sb.toString();
        log.info(createConstraintSql);
        return createConstraintSql;
    }

    @Override
    public List<ConstraintDto> getConstraintDtos(String schema) {
        return Lists.newArrayList();
    }

    @Override
    public List<ConstraintDto> getConstraintDtos(String schema, String table) {
        return Lists.newArrayList();
    }

    @Override
    public ConstraintDto getConstraintDto(String schema, ConstraintDto constraintDto) {
        return null;
    }
}
